#include "precompiled.h"
#pragma hdrstop

#include "../framework/FileSystem.h"
#include <stdarg.h>

namespace inf {
    /*
    ================
    LexerError::LexerError
    ================
    */
    LexerError::LexerError( ErrorType type, int line, const char *expected, const char *found ) {
        this->type = type;
        this->line = line;
        if ( expected != NULL )
            this->expected = expected;
        if ( found != NULL )
            this->found = found;
    }

    /*
    ================
    LexerError::ToString
    ================
    */
    void LexerError::ToString( inf::Str &result ) {
        using namespace inf;
        switch( type ) {
        case CUSTOM:
            sPrintf(result, "error %s on line %d\n", expected.c_str(), line);
            return;
        case BAD_TOKEN:
            sPrintf(result, "Expected token of type %s, found %s on line %d\n", expected, found, line);
            return;
        case MISSING_TOKEN:
            sPrintf(result, "Expected token %s, found %s on line %d\n", expected, found, line);
            return;
        case END_OF_FILE:
            sPrintf(result, "Unexpected End of File\n");
            return;
        default:
            sPrintf(result, "Unknown lexer error\n");
            return;
        }
    }

    /*
    ==============================================================================

    Token

    ==============================================================================
    */

    /*
    ================
    Token::Token
    ================
    */
    Token::Token( const char *data ) {
        string = data;
    }

    /*
    ================
    Token::GetString
    ================
    */
    const char *Token::GetString( void ) const {
        return string;
    }

    /*
    ================
    Token::GetDouble
    ================
    */
    double Token::GetDouble( void ) const {
        return inf::Str::to_double(string);
    }

    /*
    ================
    Token::GetFloat
    ================
    */
    float Token::GetFloat( void ) const {
        return inf::Str::to_float(string);
    }

    /*
    ================
    Token::GetInteger
    ================
    */
    int Token::GetInteger( void ) const {
        return inf::Str::to_integer(string);
    }

    /*
    ==============================================================================

    Lexer

    ==============================================================================
    */

    /*
    ================
    Lexer::Lexer
    ================
    */
    Lexer::Lexer( int _flags ) : token(tokenBuffer), errorInfo(LexerError::CUSTOM) {
        tokenBuffer[0] = '\0';
        buffer = NULL;
        bufferIsFile = false;
        flags = _flags;
        warningTriggered = false;
        hasByteOrderMark = false;
        tokIsUnread = true;
        tokPos = -1;

        useEscapeChars = true;
        SetSingleTokenChars( "{}(),;$=/" );
        SetCommentStrings( "//", "/*", "*/" );
    }

    /*
    ================
    Lexer::~Lexer
    ================
    */
    Lexer::~Lexer() {
        if ( buffer ) {
            if ( !bufferIsFile )
                delete[] buffer;
            else {
                //! @todo	error
                if ( fs != NULL )
                    fs->FreeFile(buffer);
            }
        }
    }

    /*
    ================
    Lexer::SetSingleTokenChars
    ================
    */
    void Lexer::SetSingleTokenChars( const char *chars ) {
        singleTokenChars = chars;
    }

    /*
    ================
    Lexer::SetCommentStrings
    ================
    */
    void Lexer::SetCommentStrings( const char *line, const char *blockStart, const char *blockEnd ) {
        lineComment = line;
        blockComment[0] = blockStart;
        blockComment[1] = blockEnd;
    }

    /*
    ================
    Lexer::error
    ================
    */
    LexerError* Lexer::Error( const char *message ) {
        errorInfo.type = LexerError::CUSTOM;
        errorInfo.line = token.line;
        errorInfo.expected = message;
        return &errorInfo;
    }

    /*
    ================
    Lexer::warning
    ================
    */
    void Lexer::Warning( const char *message ) {
        warningTriggered = true;
        if ( flags & LEXER_NOWARNINGS )
            return;

        inf::error("%s - file %s, line %d\n", message, name, token.line);
    }

    /*
    ================
    Lexer::ReadToken
    ================
    */
    const Token *Lexer::ReadToken( void ) {
        if ( tokPos == -1 )
            tokPos = 0;
        else if ( tokIsUnread ) {
            tokIsUnread = false;
            return &token;
        }
        while ( bufPos < bufSize ) {
            // Single line comments
            if ( !lineComment.is_empty() &&
                inf::Str::cmp_prefix((const char *)(buffer+bufPos), lineComment.c_str()) == 0 ) {
                    if ( !(flags & LEXER_FULL_LINES) && tokPos > 0 ) {
                        FinishToken();
                        return &token;
                    }
                    bufPos += 2;
                    if ( tokPos > 0 ) {
                        SkipRestOfLine();
                        return &token;
                    }
                    SkipRestOfLine();

                    continue;
            }
            // Multiline comments
            else if ( !blockComment[0].is_empty() &&
                inf::Str::cmp_prefix((const char *)(buffer+bufPos), blockComment[0].c_str()) == 0 ) {
                    if ( !(flags & LEXER_FULL_LINES) && tokPos > 0 ) {
                        FinishToken();
                        return &token;
                    }
                    bufPos += 2;
                    int l = line;
                    SkipToCommentEnd(); 
                    // If changed line during comment, finish the line.
                    if ( (flags & LEXER_FULL_LINES) && l != line && tokPos > 0 ) {
                        FinishToken();
                        return &token;
                    }
                    continue;
            }

            switch( buffer[bufPos] ) {
                // Ignore whitespaces
            case ' ':
            case '\t':
                SkipWhiteSpaces();
                if ( flags & LEXER_FULL_LINES )
                    AddToToken( ' ' );
                else if ( tokPos > 0 ) {
                    FinishToken();
                    return &token;
                }
                continue;

                // Newlines
            case '\n':
            case '\r':
                if ( !tokPos ) {
                    FinishLine();
                    continue;
                }
                FinishLine();
                return &token;

                // Quoted tokens
            case '\"':
            case '\'':
                if ( flags & LEXER_FULL_LINES ) {
                    ParseQuoted();
                    continue;
                }
                if ( tokPos > 0 ) {
                    FinishToken();
                    return &token;
                }
                ParseQuoted();
                return &token;

            default:
                // Check for single char tokens
                if ( (flags & LEXER_FULL_LINES) == 0 && !singleTokenChars.is_empty() ) {
                    char c = buffer[bufPos];
                    const char *chars = singleTokenChars.c_str();
                    int num = singleTokenChars.length();
                    for( int i=0; i<num; i++ ) {
                        if ( c != chars[i] )
                            continue;
                        if ( tokPos == 0 ) {
                            AddToToken( buffer[bufPos] );
                            bufPos++;
                        }
                        FinishToken();
                        return &token;
                    }
                }
                // Add this char to the token
                AddToToken( buffer[bufPos] );
                bufPos++;
                continue;
            }
        }
        if ( tokPos > 0 ) {
            FinishToken();
            return &token;
        }
        line = 0;

        return NULL;
    }

    /*
    ================
    Lexer::UnreadToken
    ================
    */
    void Lexer::UnreadToken( void ) {
        assert( !tokIsUnread );
        tokIsUnread = true;
    }

    /*
    ================
    Lexer::ExpectToken
    ================
    */
    LexerError* Lexer::ExpectToken( const char *string ) {
        if ( !ReadToken() ) {
            SetError( LexerError::END_OF_FILE );
            return &errorInfo;
        }else if ( inf::Str::cmp( token.GetString(), string, false ) != 0 ) {
            SetError( LexerError::MISSING_TOKEN, token.line, string, token.GetString() );
            return &errorInfo;
        }
        return NULL; // OK!
    }

    /*
    ================
    Lexer::CheckToken
    ================
    */
    bool Lexer::CheckToken( const char *string ) {
        if ( !ReadToken() ) {
            SetError( LexerError::END_OF_FILE );
            return false;
        }
        if ( inf::Str::cmp( token.GetString(), string, false ) != 0 ) {
            UnreadToken();
            return false;
        }
        return true;
    }

    /*
    ================
    Lexer::ReadInt
    ================
    */
    int Lexer::ReadInt( void ) {
        if ( !ReadToken() ) {
            SetError( LexerError::END_OF_FILE );
            return -1;
        }
        if ( !inf::Str::is_numeric( token.GetString() ) ) {
            SetError( LexerError::BAD_TOKEN, token.line, "integer", token.GetString() );
            return -1;
        }
        return inf::Str::to_integer( token.GetString() );
    }

    /*
    ================
    Lexer::ReadFloat
    ================
    */
    float Lexer::ReadFloat( void ) {
        if ( !ReadToken() ) {
            SetError( LexerError::END_OF_FILE );
            return inf::math::INFINITY;
        }
        if ( !inf::Str::is_numeric( token.GetString() ) ) {
            SetError( LexerError::BAD_TOKEN, token.line, "float", token.GetString() );
            return inf::math::INFINITY;
        }
        return inf::Str::to_float( token.GetString() );
    }

    /*
    ================
    Lexer::ReadString
    ================
    */
    const char *Lexer::ReadString( void ) {
        if ( !ReadToken() ) {
            SetError( LexerError::END_OF_FILE );
            return NULL;
        }
        return token.GetString();
    }

    /*
    ================
    Lexer::GetNextLine
    ================
    */
    bool Lexer::GotoNextLine( void ) {
        int oldLine = token.line;
        while( ReadToken() ) {
            if ( token.line > oldLine ) {
                UnreadToken();
                return true;
            }
        }
        return false;
    }

    /*
    ================
    Lexer::FindToken
    ================
    */
    void Lexer::FindToken( const char *str ) {
        while ( ReadToken() ) {
            if ( inf::Str::cmp( token.GetString(), str, false ) == 0 )
                return;
        }
        throw LexerError( LexerError::MISSING_TOKEN, token.line, str, "[EOF]" );
    }

    /*
    ================
    Lexer::LoadFile
    ================
    */
    bool Lexer::LoadFile( const char *filename ) {
        using namespace inf;

        if ( fileSystem == NULL )
            return false;

        buffer = NULL;
        name = filename;
        bufSize = fileSystem->ReadFile( name, (void**)&buffer);
        if ( bufSize == -1 )
            return false;

        inf::byte bom[] = { 0xEF, 0xBB, 0xBF };
        hasByteOrderMark = memcmp( buffer, bom, 3) == 0;
        if ( hasByteOrderMark )
            bufPos = 3;
        else {
            bufPos = 0;
            if ( !(flags & LEXER_NO_BOM_WARNING) )
                inf::warning( "File missing BOM: %s\n", filename );
        }

        bufferIsFile = true;

        tokPos = -1;
        line = 1;
        return true;
    }

    /*
    ================
    Lexer::LoadData
    ================
    */
    bool Lexer::LoadData( const char *dataName, const inf::byte *data, int size ) {
        if ( !dataName || !data || size < 1 )
            return false;

        inf::byte bom[] = { 0xEF, 0xBB, 0xBF };
        hasByteOrderMark = memcmp( buffer, bom, 3) == 0;
        if ( hasByteOrderMark )
            bufPos = 3;
        else {
            bufPos = 0;
            if ( !(flags & LEXER_NO_BOM_WARNING) )
                inf::warning( "File missing BOM: %s\n", dataName );
        }

        buffer = new inf::byte[size];
        memcpy(buffer, data, size);
        name = dataName;
        bufSize = size;

        bufferIsFile = false;
        tokPos = -1;
        line = 1;
        return true;
    }

    /*
    ================
    Lexer::ParseQuoted
    ================
    */
    void Lexer::ParseQuoted( void ) {
        char c = buffer[bufPos];
        if ( flags & LEXER_FULL_LINES )
            AddToToken( buffer[bufPos] );

        bufPos++;

        while ( bufPos < bufSize ) {
            // Check for newlines
            if ( buffer[bufPos] == '\n' || buffer[bufPos] == '\r' ) {
                inf::Str info;
                sPrintf(info, "quote end: \\%s", c);
                throw LexerError( LexerError::BAD_TOKEN, token.line,  info, "[EOL]" );
            }

            // Check for escape characters
            if ( useEscapeChars && buffer[bufPos] == '\\' ) {
                if ( buffer[bufPos+1] == 'n' ) {
                    AddToToken( '\n' );
                    bufPos += 2;
                    continue;
                } else if ( buffer[bufPos+1] == 't' ) {
                    AddToToken( '\t' );
                    bufPos += 2;
                    continue;
                } else if ( buffer[bufPos+1] == '\"' || buffer[bufPos+1] == '\'' ) {
                    AddToToken( buffer[bufPos+1] );
                    bufPos += 2;
                    continue;
                }
            }
            // Check if this is the quote end we are looking for.
            else if ( buffer[bufPos] == c ) {
                if ( flags & LEXER_FULL_LINES )
                    AddToToken( buffer[bufPos] );
                else
                    FinishToken( true );
                bufPos++;
                return;
            }

            AddToToken( buffer[bufPos] );
            bufPos++;
        }
        throw LexerError( LexerError::BAD_TOKEN, token.line, inf::Str("quote end: "), "[EOF]" );
    }

    /*
    ================
    Lexer::SkipToCommentEnd

    For multi line comments
    ================
    */
    void Lexer::SkipToCommentEnd( void ) {
        while ( bufPos < bufSize ) {
            if ( inf::Str::cmp_prefix((const char *)(buffer+bufPos), blockComment[1].c_str()) == 0 ) {
                bufPos += blockComment[1].length();
                return;
            }
            if ( buffer[bufPos] == '\n' )
                line++;
            bufPos++;
        }
    }

    /*
    ================
    Lexer::SkipRestOfLine
    ================
    */
    void Lexer::SkipRestOfLine( void ) {
        while ( bufPos < bufSize ) {
            if ( buffer[bufPos] == '\n' || buffer[bufPos] == '\r' ) {
                FinishLine();
                return;
            }
            bufPos++;
        }
        // End of file
    }

    /*
    ================
    Lexer::SkipWhiteSpaces
    ================
    */
    void Lexer::SkipWhiteSpaces( void ) {
        bufPos++;
        while ( bufPos < bufSize && (buffer[bufPos] == ' ' || buffer[bufPos] == '\t') )
            bufPos++;
    }

    /*
    ================
    Lexer::FinishLine
    ================
    */
    void Lexer::FinishLine( void ) {
        if ( buffer[bufPos] == '\r' )
            bufPos += 2; // skip \n too
        else
            bufPos++;

        FinishToken();
        line++;
    }

    /*
    ================
    Lexer::FinishToken
    ================
    */
    void Lexer::FinishToken( bool allowEmpty ) {
        if ( tokPos || allowEmpty ) {
            tokenBuffer[tokPos] = '\0';
            token.line = line;
            tokPos = 0;
            tokIsUnread = false;
        }
    }

    /*
    ================
    Lexer::AddToToken
    ================
    */
    void Lexer::AddToToken( char c ) {
        if ( !hasByteOrderMark && c < 0 )
            tokenBuffer[tokPos] = '?';
        else
            tokenBuffer[tokPos] = c; 
        tokPos++;
    }


}   // ::inf
